
package horde.patterns;

import horde.control.AutomatonBehavior;
import horde.control.DummyBehavior;
import horde.interfaces.ItfBehavior;
import horde.interfaces.Requests;
import horde.internalResources.HorrorControl.HorrorControl;
import horde.internalResources.dictionaries.dictionary;
import horde.utils.SynchronizedQueue;
import horde.utils.Toolbox;

/**
 * Implementation for the agent thread, generic for every agent, except the master
 * @author Andres Picazo Cuesta
 * @author Arturo Mazon Tribiño
 */
public class Agent implements Runnable {

    public Agent (String ID, ItfBehavior behavior, Core core, Requests requests) {
        this.ID = ID;
        this.messageBox = null;
        this.behavior = behavior;
        this.coreWorker = core;
        this.requests = requests;
        this.active = true;
        /* Ensure the behavior knows its core */
        behavior.setCore(coreWorker);
        coreWorker.setRequests(this.requests);
    }

    public Agent (String id, String behaviorPath, String corePath) {
        /* Sets the default language */
        dictionary.setBundle(defaultLanguage);
        this.ID = id;
        this.messageBox = null;
        this.requests = null;
        hc = HorrorControl.getInstance("ES");
        if (behaviorPath == null) this.behavior = new DummyBehavior();
        else this.behavior = new AutomatonBehavior (behaviorPath);
        try {
            coreWorker = (Core)(Class.forName(corePath).newInstance());
            /* Ensure the behavior knows its core */
            behavior.setCore(coreWorker);
            coreWorker.setID(id);
            active = true;
        } catch (Exception e) {
            System.err.println ("\nCore Error\n");
            String error = dictionary.getString("agent.coreLoadFail") + " for " + ID + " at " + behaviorPath;
            hc.trace(id, error, HorrorControl.ERROR_APPLICATION);
            this.active = false;
        }
    }
    /**
     * Assigns a new message box to get messages, allowing communication with the agent
     * @param box Assigned box
     */
    public void setMessageBox (SynchronizedQueue box) { messageBox = box; }
    /**
     * Assigns the behavior for the agent, giving the agent it's control
     * @param b Behavior to be expected from the agent
     */
    public void setBehavior (ItfBehavior b) { behavior = b; }
    /**
     * Sets the working core for the agent. This is, the class container for the
     * tasks the agent should be able to complete.
     * @param core Core to be assigned to the agent
     */
    public void setCore (Core core) { 
        coreWorker = core;
        behavior.setCore(coreWorker);
    }
    /**
     * Gets the message box assigned to the agent, if any
     * @return Currently assigned message box, or null if there's none.
     */
    public SynchronizedQueue getMessageBox () { return messageBox; }
    /**
     * Gets the behavior assigned to the agent, if any
     * @return Currently assigned behaviour , or null if there is none.
     */
    public ItfBehavior getBehavior () { return behavior; }
    /**
     * Gets the core assigned to the agent, if any.
     * @return Currently assigned core (work methods container), or null if there is none.
     */
    public Core getCore () { return coreWorker; }
    /**
     * Sets the object able to receive and process requests for the master agent
     * @param requests Linker to the master agent, usually contained in the directory
     */
    public void setRequests (Requests requests) {
        this.requests = requests;
        coreWorker.setRequests(this.requests);
    }
    /**
     * Sets the agent identificator
     * @param id Agent identificator.
     */
    public void setID (String id) { this.ID = id; }
    /**
     * Gets the agent identificator
     * @return Agent identificator (name)
     */
    public String getID () { return ID; }

    /**
     * Starts the agent thread, which is a loop where the agent looks if it has any
     * message in it's box to attend to, if any, will ask it's assigned behaviour to
     * process the message using the assigned core to do the actual work. If there is
     * no message the thread will stop in a wait() method, until a message is received.
     */
    public void run() {
        boolean processOK = true;
        try {
            while (active) {
                Object obj = messageBox.poll();
                /* Check if i have received a message */
                if (obj.getClass().getCanonicalName().equals(Msg.class.getCanonicalName())) {
                    Msg msg = (Msg) obj;
                    /* Request received */
                    if (msg.getType().equals(Msg.TYPE_REQUEST)) {
                        processOK = behavior.process(msg);
                        if (!processOK) {
                            hc.trace(ID,
                                    dictionary.getString("agent.ProcessFail") + msg.getTrunk()[0].toString()
                                    , HorrorControl.TRACE_WARNING);
                        }
                        else {
                            hc.trace(ID,
                                    dictionary.getString("agent.ProcessOk") + msg.getTrunk()[0].toString()
                                    , HorrorControl.TRACE_INFO);
                        }
                    }
                    /* Control message received */
                    else if (msg.getType().equals(Msg.TYPE_CONTROL)) {
                        int control_request = msg.getControl();
                        switch (control_request) {
                            case Msg.STOP_REQUEST: active = false; break;/* Stops the agent */
                            default: /* Does nothing */
                        }
                    }
                }
                else {
                    hc.trace(ID, dictionary.getString("agent.NoMessage"), HorrorControl.ERROR_APPLICATION);
                }
            } System.err.println ("Stopping: " + this.ID);
        } catch (Exception e) {
//            requests.trace(ID, "RUN_EXCEPTION", Requests.TRACE_ERROR);
            System.err.println (e.getMessage());
        }
    }

    /**
     * Old method to send a message, uses the deprecated method sendMessage from the 
     * requests object, so it's use should be avoided
     * @param request Request of the message
     * @param destination Who should receive the message
     * @param parameters Any parameter needed for the receiver to process the message request
     * @return true if the message was delivered, false otherwise
     * @deprecated
     */
    @Deprecated
    public boolean sendMsg (String request, String destination, Object ... parameters) {
        Object [] input = new Object[parameters.length + 1];
        input[0] = request;
        for (int i = 1; i < parameters.length + 1; i++) input[i] = parameters[i-1];
        Msg msg = new Msg(ID, destination, (new Toolbox()).getIP(), Msg.UNKNOWN, Msg.TYPE_REQUEST, input);
        return requests.sendMessage(msg);
    }

    /**
     * Method to send an already created message to the master, through the requests object
     * @param msg The message to be delivered
     * @return true if the message was successfully delivered, false otherwise
     */
    public boolean post (Msg msg) {
//        trace ("Post: " + msg.getTrunk()[0]);
        return requests.post(msg);
    }

    

    /* Atributes */
    /**
     * Resides outside, where do i look for new messages
     */
    protected SynchronizedQueue messageBox;
    /**
     * My behavior engine
     */
    protected ItfBehavior behavior;
    /**
     * My working component
     */
    protected Core coreWorker;
    /**
     * Object to access some of the master's methods
     */
    protected Requests requests;
    /**
     * Saves if the agent must continue or not taking actions
     */
    protected boolean active;
    /**
     * Saves what is doing the agent
     */
    protected String status;
    /**
     * This agent's id
     */
    protected String ID;
    /**
     * The horrorControl, which processes traces for the agent
     */
    protected HorrorControl hc;
    /**
     * Default language for the agent internationalization
     */
    private static String defaultLanguage = "ES";
    /**
     * Static variable to compare the status "working"
     */
    public static final String AGENT_WORKING = "working";
    /**
     * Static variable to compare the status "waiting"
     */
    public static final String AGENT_WAITING = "waiting";
    /**
     * Static variable to compare the status "stopped"
     */
    public static final String AGENT_STOPPED = "stopped";

    /* ERROR MESSAGES */
    protected final String NOT_A_MESSAGE = "Received object is not a message (Msg)";
    protected final String MSG_PROCESS_OK = "Msg processed ";
    protected final String MSG_PROCESS_FAIL = "Fail to process message ";
}
